{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 파트 2: 연합 학습(Federated Learning) 소개\n",
    "\n",
    "지난 섹션에서 우리는 사생활을 보호하는 딥러닝에 필요한 기본 인프라를 만드는 포인터 텐서에 대해 배웠습니다. 이번 섹션에서는 이러한 기본 도구를 이용하여 첫 사생활 보호 딥러닝 알고리즘인 연합 학습을 구현하는 방법에 대해 살펴볼 것입니다.  \n",
    "\n",
    "저자:\n",
    "- Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
    "\n",
    "역자:\n",
    "- Wonsuk Kim - Github: [@wonderit](https://github.com/wonderit) \n",
    "\n",
    "### 연합 학습이란?\n",
    "\n",
    "연합 학습이란 딥러닝 모델을 학습하는 간단하고 강력한 방법입니다. 학습 데이터는 항상 일종의 수집 프로세스의 결과입니다. 사람들은 (각종 도구를 이용해) 실제 생활에서 이벤트를 기록하면서 데이터를 생성합니다. 일반적으로 이 데이터는 머신러닝 모델을 학습할 수 있도록 중앙의 한 곳으로 집계됩니다. 연합 학습은 이 방식을 완전히 바꿔놓습니다. \n",
    "\n",
    "학습 데이터를 모델(중앙 서버)로 가져오는 대신에 모델을 학습 데이터(사용 가능한 모든 곳)로 가져가게 됩니다. \n",
    "\n",
    "이를 통해 데이터를 만드는 사람만이 영구 사본의 소유자가 되어서 액세스 권한이 있는 사람들을 제어할 수 있게 됩니다. 근사하죠?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 섹션 2.1 - 간단한 연합 학습 예제\n",
    "\n",
    "간단한 모델을 중앙 집중식으로 학습하는 것에서 시작해봅시다. 이보다 간단할 수 없습니다. 먼저 필요한 것은:\n",
    "\n",
    "- 간단한 데이터셋\n",
    "- 모델 \n",
    "- 데이터를 맞추기 위해 모델을 학습시키기 위한 몇 가지 기본 학습 방식\n",
    "\n",
    "참고: 이 API가 익숙하지 않은 경우 - 이 튜토리얼을 진행하기 전에 [fast.ai](http://fast.ai)로 이동해서 코스를 수강하세요. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch import optim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "\n",
    "# A Toy Dataset\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# A Toy Model\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "def train():\n",
    "    # Training Logic\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(20):\n",
    "\n",
    "        # 1) erase previous gradients (if they exist)\n",
    "        opt.zero_grad()\n",
    "\n",
    "        # 2) make a prediction\n",
    "        pred = model(data)\n",
    "\n",
    "        # 3) calculate how much we missed\n",
    "        loss = ((pred - target)**2).sum()\n",
    "\n",
    "        # 4) figure out which weights caused us to miss\n",
    "        loss.backward()\n",
    "\n",
    "        # 5) change those weights\n",
    "        opt.step()\n",
    "\n",
    "        # 6) print our progress\n",
    "        print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "완성입니다! 우리는 전통적인 방식으로 기본 모델을 학습시켰습니다. 모든 데이터는 로컬 컴퓨터로 집계되었으며 이를 사용하여 모델을 업데이트할 수 있습니다. 하지만 연합 학습은 이 방식으로 작동하지 않습니다. 이 예제를 연합 학습 방식으로 수정해 봅시다.  \n",
    "\n",
    "그래서 우리가 필요한 것은:\n",
    "\n",
    "- 몇 개의 작업자 만들기\n",
    "- 각 작업자의 학습 데이터에 대한 포인터를 얻기\n",
    "- 연합 학습을 수행하기 위해 업데이트 된 학습 방법\n",
    "\n",
    "    새로운 학습 단계:\n",
    "    - 올바른 작업자에게 모델을 보냅니다.\n",
    "    - 거기에 있는 데이터를 학습시킵니다. \n",
    "    - 모델을 다시 가져오고 다음 작업자에서 이를 반복합니다. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "import syft as sy\n",
    "hook = sy.TorchHook(torch)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "# create a couple workers\n",
    "\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "# A Toy Dataset\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# get pointers to training data on each worker by\n",
    "# sending some training data to bob and alice\n",
    "data_bob = data[0:2]\n",
    "target_bob = target[0:2]\n",
    "\n",
    "data_alice = data[2:]\n",
    "target_alice = target[2:]\n",
    "\n",
    "# Iniitalize A Toy Model\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "data_bob = data_bob.send(bob)\n",
    "data_alice = data_alice.send(alice)\n",
    "target_bob = target_bob.send(bob)\n",
    "target_alice = target_alice.send(alice)\n",
    "\n",
    "# organize pointers into a list\n",
    "datasets = [(data_bob,target_bob),(data_alice,target_alice)]\n",
    "\n",
    "opt = optim.SGD(params=model.parameters(),lr=0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "def train():\n",
    "    # Training Logic\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(10):\n",
    "        \n",
    "        # NEW) iterate through each worker's dataset\n",
    "        for data,target in datasets:\n",
    "            \n",
    "            # NEW) send model to correct worker\n",
    "            model.send(data.location)\n",
    "\n",
    "            # 1) erase previous gradients (if they exist)\n",
    "            opt.zero_grad()\n",
    "\n",
    "            # 2) make a prediction\n",
    "            pred = model(data)\n",
    "\n",
    "            # 3) calculate how much we missed\n",
    "            loss = ((pred - target)**2).sum()\n",
    "\n",
    "            # 4) figure out which weights caused us to miss\n",
    "            loss.backward()\n",
    "\n",
    "            # 5) change those weights\n",
    "            opt.step()\n",
    "            \n",
    "            # NEW) get model (with gradients)\n",
    "            model.get()\n",
    "\n",
    "            # 6) print our progress\n",
    "            print(loss.get()) # NEW) slight edit... need to call .get() on loss\\\n",
    "    \n",
    "# federated averaging"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 잘 하셨습니다!\n",
    "\n",
    "그리고 짜잔! 우리는 이제 연합 학습을 이용하여 매우 간단한 딥러닝 모델을 학습시키고 있습니다! 모델을 각 작업자에게 보내고 새 그래디언트를 생성한 다음 그래디언트를 로컬 서버로 가져와서 글로벌 모델을 업데이트합니다. 이 과정에서 우리는 절대 기초 학습 데이터를 보거나 요청하지 않습니다! 우리는 Bob과 Alice의 프라이버시를 유지합니다!!!\n",
    "\n",
    "## 이 예제의 단점\n",
    "\n",
    "\n",
    "따라서 이 예제는 연합 학습에 대한 좋은 소개이지만 여전히 몇 가지 중요한 단점이 있습니다. 특히 `model.get()` 를 호출하고 Bob 또는 Alice로부터 업데이트 된 모델을 수신하면 실제로 우리는 그 그래디언트를 보면서 Bob과 Alice의 학습 데이터에 대해 많은 것을 배울 수 있습니다. 경우에 따라 학습 데이터를 완벽하게 복원도 가능합니다!\n",
    "\n",
    "그래서 어떻게 해야할까요? 음, 사람들이 사용하는 첫번째 전략으로는 중앙 서버에 업로드하기 전에 **여러 개인의 그래디언트를 평균화하는 것입니다**. 그러나 이 전략을 사용하려면 PointerTensor 객체를 좀 더 정교하게 사용해야 합니다. 다음 섹션에서는 포인터의 고급 기능에 대해 배우고 연합 학습 예제를 업그레이드하도록 하겠습니다. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 축하합니다!!! - 이제 커뮤니티에 가입할 시간입니다!\n",
    "\n",
    "이 파이썬 노트북 튜토리얼을 완료한 것을 축하합니다! 이 튜토리얼에 만족하셨다면, 그리고 AI와 AI 공급망(데이터)의 사생활을 보존하고 탈중앙화된 소유권을 부여하는 움직임에 동참하고 싶다면 다음과 같이하십시오!\n",
    "\n",
    "### PySyft GitHub의 Star버튼을 누르세요\n",
    "\n",
    "커뮤니티를 돕는 가장 쉬운 방법은 GitHub 리포지토리를 널리 알리는 것입니다! 이를 통해 우리가 만드는 멋진 도구에 대한 인식을 높일 수 있습니다.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### 우리 Slack에 가입하세요!\n",
    "\n",
    "최신 소식을 받는 가장 좋은 방법은 커뮤니티에 가입하는 것입니다! [http://slack.openmined.org](http://slack.openmined.org)에서 양식을 작성하면 됩니다.\n",
    "\n",
    "### Code Project에 참여하십시오!\n",
    "\n",
    "커뮤니티에 기여하는 가장 좋은 방법은 code contributor가 되는 것입니다! 언제든지 PySyft GitHub 이슈 페이지로 이동하여 \"Projects\"를 필터링 할 수 있습니다. 그러면 참여할 수 있는 프로젝트에 대한 개요를 제공하는 모든 최상위 티켓이 표시됩니다. 프로젝트에 참여하고 싶지는 않지만 약간의 코딩을 원한다면 \"good first issue\"로 표시된 GitHub 문제를 검색하여 더 많은 \"일회용\" 미니 프로젝트를 찾을 수도 있습니다.\n",
    "\n",
    "- [PySyft Projects](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Good First Issue Tickets](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### 기부하기\n",
    "\n",
    "코드에 기여할 시간이 없지만 지원을 하고 싶다면 Open Collective의 후원자가 될 수도 있습니다. 모든 기부금은 웹 호스팅 및 해커톤 및 밋업과 같은 기타 커뮤니티 비용으로 들어갑니다!\n",
    "\n",
    "[OpenMined's Open Collective Page](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "is_executing": false
    }
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "language": "python",
   "display_name": "Python 3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  },
  "pycharm": {
   "stem_cell": {
    "cell_type": "raw",
    "source": [],
    "metadata": {
     "collapsed": false
    }
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}